package com.laopan.elasticsearch.handler;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.laopan.elasticsearch.bean.Page;
import com.laopan.elasticsearch.bean.geo.QueryGeoResult;
import com.laopan.elasticsearch.bean.geo.*;
import com.laopan.elasticsearch.core.geo.GeoQuery;
import com.laopan.elasticsearch.utils.JsonUtils;
import org.apache.lucene.search.TotalHits;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;

/**
 * @author laopan
 * @className AbstractGeoQuery
 * @date 2021/4/20 13:44
 */

public abstract class AbstractGeoQuery<T> extends AbstractIndexInfo {
    private GeoQuery esGeoQuery;

    public AbstractGeoQuery(GeoQuery esGeoQuery) {
        this.esGeoQuery = esGeoQuery;
    }

    public GeoQuery getEsGeoQuery() {
        return esGeoQuery;
    }

    public void setEsGeoQuery(GeoQuery esGeoQuery) {
        this.esGeoQuery = esGeoQuery;
    }

    /**
     * 根据id查询数据信息并返回距离信息
     *
     * @param id
     * @param distancePoint
     * @return
     */
    public DistanceResult<T> queryById(String id, Location distancePoint) {
        SearchResponse resp = esGeoQuery.queryById(getIndex(), getLocationFieldName(), new IdGeo( id, distancePoint));
        List<DistanceResult<T>> result = getResult(resp,distancePoint);
        if (!result.isEmpty()) {
            return result.get(0);
        }
        return null;
    }


    public List<DistanceResult<T>> screen(Screen screen) {
        SearchResponse resp = esGeoQuery.screen(getIndex(), getLocationFieldName(),new Page(getMaxSize()) , screen);
        return getResult(resp,screen.getCenter());
    }

    public List<DistanceResult<T>> termScreen(Screen screen) {
        SearchResponse resp = esGeoQuery.termScreen(getIndex(), getLocationFieldName(), new Page(getMaxSize()), screen);
        return getResult(resp,screen.getCenter());

    }
    public QueryGeoResult<T> search(GeoSearch search) {
        return search(search,null);
    }
    public QueryGeoResult<T> search(GeoSearch search,Page page) {
        SearchResponse resp = esGeoQuery.multiTermSearch(getIndex(), getLocationFieldName(), search,page);
        return convertData(resp,search.getPoint());
    }


    public List<DistanceResult<T>> centerRange(CenterRange centerRange) {
        SearchResponse resp = esGeoQuery.centerRange(getIndex(), getLocationFieldName(), new Page(getMaxSize()), centerRange);
        return getResult(resp,centerRange.getDistancePoint());
    }

    public List<DistanceResult<T>> termCenterRange(CenterRange centerRange) {
        SearchResponse resp = esGeoQuery.termCenterRange(getIndex(), getLocationFieldName(), new Page(getMaxSize()), centerRange);
        return getResult(resp,centerRange.getDistancePoint());
    }

    public QueryGeoResult<T> convertData(SearchResponse resp,Location distancePoint) {
        if (null != resp) {
            SearchHits searchHits = resp.getHits();
            if (null != searchHits) {
                TotalHits totalHits = searchHits.getTotalHits();
                List<DistanceResult<T>> dataList = getResult(resp,distancePoint);
                return new QueryGeoResult<>(totalHits.value, dataList);
            }
        }
        return new QueryGeoResult<>(0,new ArrayList<>());
    }

    public List<DistanceResult<T>> getResult(SearchResponse resp,Location distancePoint) {
        if (null == resp || null == resp.getHits()) {
            return new ArrayList<>();
        }
        SearchHits searchHits = resp.getHits();
        SearchHit[] hits = searchHits.getHits();
        List<DistanceResult<T>> dataList = new ArrayList<>();
        T data;
        if (null != hits && hits.length > 0) {
            for (SearchHit hit : hits) {
                data = JsonUtils.fromJson(hit.getSourceAsString(), getEntityClass());
                dataList.add(new DistanceResult<>(getDistance(data,distancePoint), data));
            }
        }
        return dataList;
    }
    public  String getDistance(T data, Location distancePoint) {
        if(data==null){
            return "0";
        }
        JsonObject jsonObject=JsonUtils.toJsonTree(data);
        JsonElement element = jsonObject.get(getLocationFieldName());
        JsonObject location = element.getAsJsonObject();
        if(location==null) {
            return "0";
        }
        double  lon= location.get("lon").getAsDouble();
        double  lat= location.get("lat").getAsDouble();
        return getDistance(new Location(lon,lat),distancePoint);
    }
    public  String getDistance(Location point, Location otherPoint) {
        double distance= GeoDistance.ARC.calculate(point.getLat(), point.getLon(),otherPoint.getLat(),otherPoint.getLon(), DistanceUnit.METERS);
        BigDecimal geoDistance = BigDecimal.valueOf(distance).setScale(0,
                BigDecimal.ROUND_HALF_DOWN);
        return geoDistance.toPlainString();
    }
    /**
     * 获取做大查询数量 默认20调 如果不是重写该方法
     *
     * @return
     */
    public int getMaxSize() {
        return 20;
    }

    /**
     * 获取location字段名称，默认location 如果不是重写该方法
     *
     * @return
     */
    public String getLocationFieldName() {
        return "location";
    }

    /**
     * 获取对象
     *
     * @return
     */
    protected abstract Class<T> getEntityClass();

}
